/**
 * 
 */
package com.primestone.cim.executemeterreadschedules.service;

import java.io.Serializable;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;
import javax.sql.rowset.serial.SerialClob;

import ch.iec.tc57._2011.meterreadschedules.EndDeviceAsset;
import ch.iec.tc57._2011.meterreadschedules.MeterReadSchedules;
import ch.iec.tc57._2011.meterreadschedules.TimeSchedule;
import ch.iec.tc57._2011.meterreadschedules.TimeSchedule.ScheduleInterval;
import ch.iec.tc57._2011.meterreadschedules.TimeSchedule.TimePoints;
import ch.iec.tc57._2011.meterreadschedulesmessage.MeterReadSchedulesPayloadType;
import ch.iec.tc57._2011.schema.message.HeaderType;

import com.google.gson.Gson;
import com.primestone.cim.entities.tmp.ResMeterReadSchedules;
import com.primestone.cim.executemeterreadschedules.Completer;
import com.primestone.cim.executemeterreadschedules.ScheduleOperationResult;
import com.primestone.cim.util.CollectionUtil;
import com.primestone.cim.util.DateUtil;
import com.primestone.cim.util.NumberUtil;
import com.primestone.cim.util.StringUtil;
import com.primestone.cim.util.enums.RelayActionEnum;

/**
 * @author cesar.mateus
 *
 */
public class ExecMeterReadSchedulesService implements Serializable, IExecMeterReadSchedulesService{

	private static final long serialVersionUID = -5156403685273734812L;

	private DataSource dataSource;

	private volatile static IExecMeterReadSchedulesService instance = null;

	private ExecMeterReadSchedulesService(){}

	private ExecMeterReadSchedulesService(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public static IExecMeterReadSchedulesService getInstance(DataSource dataSource) {
		if (instance == null) {
			synchronized(ExecMeterReadSchedulesService.class){
				if (instance == null) {
					instance = new ExecMeterReadSchedulesService(dataSource);	
				}
			}
		} 
		return instance;
	}

	@Override
	public ResMeterReadSchedules getOperations(MeterReadSchedulesPayloadType payload, String ticket, HeaderType header) {
		ResMeterReadSchedules resMeterReadSchedules = null;		

		try {
			MeterReadSchedules meterReadSchedule = payload.getMeterReadSchedules();

			List<EndDeviceAsset> endDeviceAssets = meterReadSchedule.getEndDeviceAsset();
			System.out.println("---- endDeviceAssets: " +endDeviceAssets);
			List<String> mridsEndDevices = new ArrayList<String>();
			for (EndDeviceAsset endDeviceAsset : endDeviceAssets) {
				mridsEndDevices.add(endDeviceAsset.getMRID());
			}

			List<TimeSchedule> timeSchedules = meterReadSchedule.getTimeSchedule();
			List<ScheduleOperationResult> extractedSchedules = new ArrayList<ScheduleOperationResult>();
			ScheduleOperationResult scheduleOperationResult;
			ScheduleInterval scheduleInterval;
			List<Integer> dataFromRecurrencePattern;
			for (TimeSchedule timeSchedule : timeSchedules) {
				scheduleOperationResult = new ScheduleOperationResult();
				scheduleInterval = timeSchedule.getScheduleInterval();
				scheduleOperationResult.setDateTime(DateUtil.getJavaUtilDateFromXMLGregorianCalendar(scheduleInterval.getStart()));

				if (StringUtil.safe(timeSchedule.getRecurrencePattern())) {
					dataFromRecurrencePattern = Completer.getDataFromRecurrencePattern(timeSchedule.getRecurrencePattern());
					scheduleOperationResult.setPeriod(dataFromRecurrencePattern.get(0));
					scheduleOperationResult.setFrequency(dataFromRecurrencePattern.get(1));
				}

				if (CollectionUtil.safe(timeSchedule.getTimePoints())) {
					int retries = 0;
					// Hasta aqui no se ha tenido en cuenta el atributo relativeTimeinterval 
					for (TimePoints timePoint : timeSchedule.getTimePoints()) {
						System.out.println("---- timePoint.getRelativeTimeinterval: "+timePoint.getRelativeTimeinterval());
						retries++;
					}

					scheduleOperationResult.setRetries(retries);
					if (NumberUtil.safePositive(timeSchedule.getRecurrencePeriod())) {
						scheduleOperationResult.setRetryTime(timeSchedule.getRecurrencePeriod().intValue());	
					}
				}

				extractedSchedules.add(scheduleOperationResult);
			}

			resMeterReadSchedules = executeStoreProcedure(
					ticket, extractedSchedules, mridsEndDevices, header.getUser().getUserID());
		} catch( SQLException e ) {
			e.printStackTrace();
			//			try {
			//				if( conn != null ) {
			//					conn.rollback();
			//				}
			//			} catch( SQLException ex ) {
			//				ex.printStackTrace();
			//			}
		} catch( Exception e ) {
			e.printStackTrace();
			//			try {
			//				if( conn != null ) {
			//					conn.rollback();
			//				}
			//			} catch( SQLException ex ) {
			//				ex.printStackTrace();
			//			}
		}

		return resMeterReadSchedules;
	}

	/**
	 * 
	 * @param conn
	 * @param ticket
	 * @param extractedSchedules
	 * @param mridsEndDevices
	 * @param userId
	 * @return
	 * @throws Exception
	 */
	private ResMeterReadSchedules executeStoreProcedure( 
			String ticket, List<ScheduleOperationResult> extractedSchedules, 
			List<String> mridsEndDevices, String userId ) throws Exception {
		ResMeterReadSchedules resMeterReadSchedules = null;
		try {
			List<ScheduleOperationResult> schedulesToSave = new ArrayList<ScheduleOperationResult>();

			ScheduleOperationResult scheduleTmp;
			for (String idDevice : mridsEndDevices) {
				System.out.println("---- idDevice: "+idDevice);
				for (ScheduleOperationResult scheduleOperationResult : extractedSchedules) {
					scheduleTmp = new ScheduleOperationResult(scheduleOperationResult);
					scheduleTmp.setIdDevice(idDevice);
					schedulesToSave.add(scheduleTmp);
				}
			}

			System.out.println("---- mridsEndDevices: "+mridsEndDevices);
			System.out.println("---- schedulesToSave: "+schedulesToSave);
			List<ScheduleOperationResult> saveReadSchedules = saveReadSchedules(schedulesToSave);

			// Colocar los resultados en formato json
			Map<String, List<ScheduleOperationResult>> mapforJSon = new HashMap<String, List<ScheduleOperationResult>>();
			mapforJSon.put(ticket, saveReadSchedules);

			Gson gson = new Gson();
			//System.out.println("---- map res:" +mapforJSon);
			String json = gson.toJson(mapforJSon);
			//System.out.println("---- json res:" +json);

			Clob jsonClob = new SerialClob(json.toCharArray());

			resMeterReadSchedules = new ResMeterReadSchedules(ticket, userId, "", new Date(), jsonClob, StringUtil.CREATE_OPERATION);			

		} catch( Exception e ) {
			e.printStackTrace();
			throw e;
		}

		return resMeterReadSchedules;
	}

	/**
	 * 
	 * pDevice VARCHAR(50),
	 * pDatetime DATETIME, --YYYYMMDD HH:MI
	 * pPeriod SMALLINT, --0 none, 1 minute, 2 hour, 3 day , 4 week, 5 month
	 * pRetries SMALLINT,
	 * pRetritime SMALLINT,
	 * pFreq SMALLINT,
	 * pIsLP SMALLINT,
	 * pIsRG SMALLINT,
	 * pIsEV SMALLINT,
	 * pRelay VARCHAR(15) = 'None',
	 * --pIsCallSlaves SMALLINT,
	 * --pIsTimeSynch SMALLINT,
	 * Cerror VARCHAR(100) = '' output
	 * 
	 * @param conn
	 * @param startTime
	 * @param endTime
	 * @param socket
	 * @param descVar
	 * @param varType
	 * @param ticketReadings
	 * @return
	 * @throws SQLException
	 */
	private List<ScheduleOperationResult> saveReadSchedules (
			List<ScheduleOperationResult> schedulesToSave) throws Exception {
		Connection conn = null;
		CallableStatement cs = null;
		try {
			conn = this.dataSource.getConnection();
			//conn.setAutoCommit(false);
			// TODO Usar Batch Updates?: http://docs.oracle.com/javase/1.3/docs/guide/jdbc/getstart/callablestatement.html
			// TODO Hacer conn.setAutoCommit( false ); y luego de la iteracion hacer conn.commit(); ?
			String callStatement = "{ ? = call spInsertCallSchedule(?,?,?,?,?,?,?,?,?,?,?) }";
			cs = conn.prepareCall(callStatement);
			//			List<ScheduleOperationResult> savedSchedules = new ArrayList<ScheduleOperationResult>();
			//			ScheduleOperationResult savedSchedule;
			for (ScheduleOperationResult result : schedulesToSave) {
				try {
					cs.setEscapeProcessing(true);
					//cs.setQueryTimeout(1800);
					cs.registerOutParameter(1, java.sql.Types.INTEGER);
					cs.setString(2, result.getIdDevice());
					cs.setTimestamp(3, new Timestamp(result.getDateTime().getTime()));
					cs.setInt(4, result.getPeriod());
					cs.setInt(5, result.getRetries());
					if (NumberUtil.safe(result.getRetryTime())) {
						cs.setInt(6, result.getRetryTime());	
					} else {
						cs.setInt(6, 0);
					}

					cs.setInt(7, result.getFrequency());
					cs.setInt(8, 1);
					cs.setInt(9, 1);
					cs.setInt(10, 1);
					cs.setString(11, RelayActionEnum.NONE.getAction());
					cs.setString(12, StringUtil.EMPTY);
					cs.registerOutParameter(12, java.sql.Types.VARCHAR);

					cs.executeUpdate();
					System.out.println("--- output return:" + cs.getInt(1) + " msg:" + cs.getString(12));
					//conn.commit();
					result.setIsSaved(true);
					result.setMessage(StringUtil.OK);
				} catch (Exception e) {
					if( conn != null ) {
						//conn.rollback();	
					}
					
					e.printStackTrace();
					result.setIsSaved(false);
					result.setMessage(e.getMessage());
				}
			}
		}  catch (Exception e) {
			e.printStackTrace();
			throw e;
		} finally {			
			if (cs != null) {
				cs.close();	
			}
			if( conn != null ) {
				conn.close();
			}
		}

		return schedulesToSave;
	}

	protected Object clone() {
		return instance;
	}

	private Object readResolve(){
		return instance;
	}
}
